/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.classfile.instruction;

import proguard.classfile.*;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.constant.*;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.ClassUtil;

/**
 * This Instruction represents an instruction that refers to an entry in the constant pool.
 *
 * @author Eric Lafortune
 */
public class ConstantInstruction extends Instruction implements ConstantVisitor {
    public int constantIndex;
    public int constant;

    // Fields acting as return parameters for the ConstantVisitor methods.
    private int parameterStackDelta;
    private int typeStackDelta;

    /**
     * Creates an uninitialized ConstantInstruction.
     */
    public ConstantInstruction() {}

    /**
     * Creates a new ConstantInstruction with the given opcode and constant pool index.
     */
    public ConstantInstruction(byte opcode, int constantIndex) {
        this(opcode, constantIndex, 0);
    }

    /**
     * Creates a new ConstantInstruction with the given opcode, constant pool index, and constant.
     */
    public ConstantInstruction(byte opcode, int constantIndex, int constant) {
        this.opcode = opcode;
        this.constantIndex = constantIndex;
        this.constant = constant;
    }

    /**
     * Copies the given instruction into this instruction.
     * 
     * @param constantInstruction the instruction to be copied.
     * @return this instruction.
     */
    public ConstantInstruction copy(ConstantInstruction constantInstruction) {
        this.opcode = constantInstruction.opcode;
        this.constantIndex = constantInstruction.constantIndex;
        this.constant = constantInstruction.constant;

        return this;
    }

    // Implementations for Instruction.

    public byte canonicalOpcode() {
        // Remove the _w extension, if any.
        return opcode == InstructionConstants.OP_LDC_W ? InstructionConstants.OP_LDC : opcode;
    }

    public Instruction shrink() {
        // Do we need a short index or a long index?
        if (requiredConstantIndexSize() == 1) {
            // Can we replace the long instruction by a short instruction?
            if (opcode == InstructionConstants.OP_LDC_W) {
                opcode = InstructionConstants.OP_LDC;
            }
        } else {
            // Should we replace the short instruction by a long instruction?
            if (opcode == InstructionConstants.OP_LDC) {
                opcode = InstructionConstants.OP_LDC_W;
            }
        }

        return this;
    }

    protected void readInfo(byte[] code, int offset) {
        int constantIndexSize = constantIndexSize();
        int constantSize = constantSize();

        constantIndex = readValue(code, offset, constantIndexSize);
        offset += constantIndexSize;
        constant = readValue(code, offset, constantSize);
    }

    protected void writeInfo(byte[] code, int offset) {
        int constantIndexSize = constantIndexSize();
        int constantSize = constantSize();

        if (requiredConstantIndexSize() > constantIndexSize) {
            throw new IllegalArgumentException(
                "Instruction has invalid constant index size (" + this.toString(offset) + ")");
        }

        writeValue(code, offset, constantIndex, constantIndexSize);
        offset += constantIndexSize;
        writeValue(code, offset, constant, constantSize);
    }

    public int length(int offset) {
        return 1 + constantIndexSize() + constantSize();
    }

    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
        InstructionVisitor instructionVisitor) {
        instructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, this);
    }

    public int stackPopCount(Clazz clazz) {
        int stackPopCount = super.stackPopCount(clazz);

        // Some special cases.
        switch (opcode) {
            case InstructionConstants.OP_MULTIANEWARRAY:
                // For each dimension, an integer size is popped from the stack.
                stackPopCount += constant;
                break;

            case InstructionConstants.OP_PUTSTATIC:
            case InstructionConstants.OP_PUTFIELD:
                // The field value is be popped from the stack.
                clazz.constantPoolEntryAccept(constantIndex, this);
                stackPopCount += typeStackDelta;
                break;

            case InstructionConstants.OP_INVOKEVIRTUAL:
            case InstructionConstants.OP_INVOKESPECIAL:
            case InstructionConstants.OP_INVOKESTATIC:
            case InstructionConstants.OP_INVOKEINTERFACE:
            case InstructionConstants.OP_INVOKEDYNAMIC:
                // Some parameters may be popped from the stack.
                clazz.constantPoolEntryAccept(constantIndex, this);
                stackPopCount += parameterStackDelta;
                break;
        }

        return stackPopCount;
    }

    public int stackPushCount(Clazz clazz) {
        int stackPushCount = super.stackPushCount(clazz);

        // Some special cases.
        switch (opcode) {
            case InstructionConstants.OP_GETSTATIC:
            case InstructionConstants.OP_GETFIELD:
            case InstructionConstants.OP_INVOKEVIRTUAL:
            case InstructionConstants.OP_INVOKESPECIAL:
            case InstructionConstants.OP_INVOKESTATIC:
            case InstructionConstants.OP_INVOKEINTERFACE:
            case InstructionConstants.OP_INVOKEDYNAMIC:
                // The field value or a return value may be pushed onto the stack.
                clazz.constantPoolEntryAccept(constantIndex, this);
                stackPushCount += typeStackDelta;
                break;
        }

        return stackPushCount;
    }

    // Implementations for ConstantVisitor.

    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) {}

    public void visitLongConstant(Clazz clazz, LongConstant longConstant) {}

    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) {}

    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) {}

    public void visitPrimitiveArrayConstant(Clazz clazz, PrimitiveArrayConstant primitiveArrayConstant) {}

    public void visitStringConstant(Clazz clazz, StringConstant stringConstant) {}

    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) {}

    public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) {}

    public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {}

    public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) {}

    public void visitModuleConstant(Clazz clazz, ModuleConstant moduleConstant) {}

    public void visitPackageConstant(Clazz clazz, PackageConstant packageConstant) {}

    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) {
        String type = fieldrefConstant.getType(clazz);

        typeStackDelta = ClassUtil.internalTypeSize(ClassUtil.internalMethodReturnType(type));
    }

    public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) {
        clazz.constantPoolEntryAccept(invokeDynamicConstant.u2nameAndTypeIndex, this);
    }

    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) {
        clazz.constantPoolEntryAccept(interfaceMethodrefConstant.u2nameAndTypeIndex, this);
    }

    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) {
        clazz.constantPoolEntryAccept(methodrefConstant.u2nameAndTypeIndex, this);
    }

    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) {
        String type = nameAndTypeConstant.getType(clazz);

        parameterStackDelta = ClassUtil.internalMethodParameterSize(type);
        typeStackDelta = ClassUtil.internalTypeSize(ClassUtil.internalMethodReturnType(type));
    }

    // Implementations for Object.

    public String toString() {
        return getName() + " #" + constantIndex + (constantSize() == 0 ? "" : ", " + constant);
    }

    // Small utility methods.

    /**
     * Returns the constant pool index size for this instruction.
     */
    private int constantIndexSize() {
        return opcode == InstructionConstants.OP_LDC ? 1 : 2;
    }

    /**
     * Returns the constant size for this instruction.
     */
    private int constantSize() {
        return opcode == InstructionConstants.OP_MULTIANEWARRAY ? 1
            : opcode == InstructionConstants.OP_INVOKEDYNAMIC || opcode == InstructionConstants.OP_INVOKEINTERFACE ? 2
                : 0;
    }

    /**
     * Computes the required constant pool index size for this instruction's constant pool index.
     */
    private int requiredConstantIndexSize() {
        return (constantIndex & 0xff) == constantIndex ? 1 : (constantIndex & 0xffff) == constantIndex ? 2 : 4;
    }
}
